/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package UI;

import XML.CreaXMl;
import Entidades.Grafo;
import Entidades.GrafoNoDirigido;
import Entidades.Nodo;
import Excepciones.CantidadNodosSuperada;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import javax.imageio.ImageIO;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

/**
 *
 * @author Marco
 */
public class Lienzo extends JPanel implements MouseListener, MouseMotionListener {
    boolean pintarAEM; //bandera para que pinte el arbol de expansion minima
    int x = 0, y = 0;
    Linea linea;
    Circulo circulo;
    Circulo jalada = null;
    int ancho, alto;
    ArrayList<Circulo> listaNodos = new ArrayList<Circulo>();
    ArrayList<Linea> listaArcos = new ArrayList<Linea>();
    ArrayList<String> arcosAPintar = new ArrayList<String>(); // lista de arcos a pintar de dierentes colores
    VPrincipal p;
    /**
     * se crea el grafo en esta instancia para probar funcionlaidad e sus
     * metodos rapidamente, luego se sacara a dodne correspodna
     *
     * @param prin
     */
    public GrafoNoDirigido grafo;
////////////////////////////////////////////////////////////////////

    public Lienzo(VPrincipal prin) {
        p = prin;//ver que tan util y permormante ews, pero alfinal
        this.addMouseListener(this);
        this.addMouseMotionListener(this);
        this.setVisible(true);
        this.setDoubleBuffered(true);
        grafo = new GrafoNoDirigido();
        
        //this.ancho = p.getWidth() - 200;
        //this.alto = p.getHeight() - 200;
    }

    public ArrayList<String> getArcosAPintar() {
        return arcosAPintar;
    }

    public void setArcosAPintar(ArrayList<String> arcosAPintar) {
        this.arcosAPintar = arcosAPintar;
    }

    /*pinta todos los nodos e grafos incluidos en las listas*/
    @Override
    public void paintComponent(Graphics g) {
        
       
    
        super.paintComponents(g);
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, getWidth(), getHeight());
        //this.ancho = p.getWidth() - 200;
       // this.alto = p.getHeight() - 200;
        //this.setSize(ancho, alto);// con esto hacemso que el panel tenga un tamaño proporcional al frame
        this.paintBorder(g);
        for (Linea f : listaArcos) {
            if(pintarAEM){
            for(String arco:arcosAPintar){
                
                if (f.getNombre().contains(arco)){
                    f.pintarAEM=true;
                }
            }
            }
            f.painter(g);
        }

        for (Circulo f : listaNodos) {
            f.painter(g, this);
        }
        
        if(pintarAEM){
            
            g.setColor(Color.GREEN);
            g.fillRect(10, 10, 10, 10);
            g.setColor(Color.BLACK);
            g.drawString("Trayectoria correspondiente al Árbol de expansión mínima", 25, 25);
            pintarAEM=false;
        }
        
       
        
        
    }
    
    

    


    
    
    /**
     * SETTER y GETTER
     *
     */
    public ArrayList<Circulo> getListaNodos() {
        return listaNodos;
    }

    public void setListaNodos(ArrayList<Circulo> listaNodos) {
        this.listaNodos = listaNodos;
    }

    public ArrayList<Linea> getListaArcos() {
        return listaArcos;
    }

    public void setListaArcos(ArrayList<Linea> listaArcos) {
        this.listaArcos = listaArcos;
    }

    /**
     * PORCION DE COIDGO QUE SE ENCARGA DE TODA LA INTERACCION CON LOS ARCOS
     */
    /**
     * Metodo que agrega un arco entre dos nodos. Recibe como parametro los dos
     * nodos que contienen el arco, depseus de agregarlo en la UI lo agrega en
     * al estructura de datos tambien
     *
     * @param x
     * @param y
     */
    public void agregarArco(int x, int y) {
        try {
            linea = new Linea(buscarNodoPorNombre(x), buscarNodoPorNombre(y));
            this.listaArcos.add(linea);
        


            repaint();
            p.repaint();
            grafo.unir(buscarNodoPorNombre(x).numeroNodo, buscarNodoPorNombre(y).numeroNodo);
        } catch (IndexOutOfBoundsException e) {
            JOptionPane.showMessageDialog(null, "No se encontro el nodo");
        }
    }

    /**
     * este metodo agregar un arco, pero uno reconstruido del xml por eso incluy
     * el peos en el constructor
     *
     * @param x
     * @param y
     * @param peso
     */
    public void agregarArco(int x, int y, int peso) {
        try {
            linea = new Linea(listaNodos.get(x), listaNodos.get(y));
            linea.setPeso(peso);
            this.listaArcos.add(linea);


            repaint();
            p.repaint();
            grafo.unir(listaNodos.get(x).numeroNodo, listaNodos.get(y).numeroNodo);
            grafo.ponderarArco(x, y, peso);
        } catch (IndexOutOfBoundsException e) {
            JOptionPane.showMessageDialog(null, "No se encontro nodo");
        }
    }
    
    /**
     * ete metodo recibe como parametro un umero de nodo (nombre) lo busca en la lista de nodos
     * y lo devuele, lo usamo para que al linea se cree con lso nodos que recibe
     */
    
    public Circulo buscarNodoPorNombre(int nombreNodo){
        for(Circulo nodo:listaNodos){
            if(nodo.getNumeroNodo()==nombreNodo)
                return nodo;
        }
        return null;
    }

    /**
     * Metodo que añade un peso al arco entre dos nodos, recibe como parametro
     * los dos nodos que contienen el arco con eso determina que arco era y ahi
     * le setea el peso, luego de hacerlo en la UI lo hace ne la estructura de
     * datos DESUSO
     *
     * @param nodoInicial
     * @param nodoFinal
     * @param peso
     */
    public void ponderarArco(int nodoInicial, int nodoFinal, int peso) {
        for (Linea l : listaArcos) {
            if (l.getFfinal().getNumeroNodo() == nodoFinal && l.getInicial().getNumeroNodo() == nodoInicial) {
                l.setPeso(peso);
                grafo.ponderarArco(nodoInicial, nodoFinal, peso);
                break;
            }

        }

        p.repaint();
    }

    /**
     * Metodo que añade un peso al arco cuyo nombr recibe pro parametro si no
     * encuentra el arco en la lista deuvelve false
     *
     * @param nombre
     * @param peso
     * @return
     */
    public boolean ponderarArco(String nombre, int peso) {
        boolean resultado = false;
        for (Linea arco : listaArcos) {
            if (arco.getNombre().contains(nombre)) {
                arco.setPeso(peso);
                grafo.ponderarArco(arco.getInicial().getNumeroNodo(), arco.getFfinal().getNumeroNodo(), peso);
                resultado = true;
                break;
            }
        }
        p.repaint();
        return resultado;

    }

    /**
     * Metodo que elimina un arco, recibe como parametro los dos nodos que que
     * lo contiene y con eso determina que arco era en la lista de arcos, luego
     * de eliminarlo en la UI lo elimina en la estructura de datos
     *
     * @param nodoInicial
     * @param nodoFinal EN DESUSO
     */
    public void eliminarArco(int nodoInicial, int nodoFinal) {
        for (Linea l : listaArcos) {
            if (l.getFfinal().getNumeroNodo() == nodoFinal && l.getInicial().getNumeroNodo() == nodoInicial) {
                listaArcos.remove(l);
                grafo.cortar(nodoInicial, nodoFinal);
                break;
            }

        }


        p.repaint();
    }

    /**
     * Metodo que elimina un arco, recibe el nombre del arco por parametro si el
     * arco no existe en la lista de arcos los borra
     *
     * @param nombre
     * @return
     */
    public boolean eliminarArco(String nombre) {
        boolean resultado = false;
        Linea arcoAEliminar = this.buscarArco(nombre);
        if (arcoAEliminar != null) {
            listaArcos.remove(arcoAEliminar);;
            grafo.cortar(arcoAEliminar.getInicial().getNumeroNodo(), arcoAEliminar.getFfinal().getNumeroNodo());
            resultado = true;
        }
        p.repaint();
        return resultado;




    }

    /**
     * este metodo recupera un arco de la lista de arcos segun el nombre
     * recibido, si no lo encuentra devuelve null
     *
     * @param nombreArco
     * @return
     */
    public Linea buscarArco(String nombreArco) {
        Linea retorno = null;
        for (Linea arco : listaArcos) {
            if (arco.getNombre().contains(nombreArco)) {
                retorno = arco;
                break;
            }
            

        }
        return retorno;
    }

    /* SEGMENTO DE COIDGO ENCARGADO DE LA MANIPULACION DE NODOS*/
    /**
     * Metodo que pondera un nodo seleccionado, recibe com parametro el nombre
     * de nodo a ponderar, luego de ponerarlo en la UI lo ponder ane la
     * estructura de datos
     *
     * @param nodoAPonderar
     * @param peso
     */
    public void ponderarNodo(int nodoAPonderar, int peso) {
        for (Circulo n : listaNodos) {
            if (n.numeroNodo == nodoAPonderar) {
                n.peso = peso;
                grafo.ponderarNodo(nodoAPonderar, peso);
                break;
            }

        }
        /*con esto hacemos, que se repinte el frame, por lo tanto 
         * se repinta el lienzo y refresca sgun sus listas d enodos y arcos
         */
        p.repaint();
    }

    /**
     * Metodo que agrega un nodo, recibe como parametro la posiciion dodne
     * agregarlo luego de agregarlo en la UI lo agrega en la estructura de datos
     *
     * @param x
     * @param y
     */
    public void agregarNodo(int x, int y) throws CantidadNodosSuperada {
        if(listaNodos.size()>=25)
            throw new CantidadNodosSuperada("Se permite un maximo de 25 nodos");
        Nodo nuevoNodo = new Nodo();
        int agregacion = grafo.agregarNodo(nuevoNodo);
        if(agregacion>-1){
            circulo = new Circulo(x, y);
            circulo.setNumeroNodo(agregacion);
            listaNodos.add(circulo);
            repaint();
            p.repaint();
        
        }
        else{
             throw new CantidadNodosSuperada("Se permite un maximo de 25 nodos");
        }
        
        
        /*agrega el nodo al grafo*/
        
        
    }

    /**
     * Estemetodo es para agregar directamente un circulo el cual se reconstruye
     * apartir del xml
     *
     * @param ci
     */
    public void agregarNodo(Circulo ci) {
        this.listaNodos.add(ci);
        repaint();
        p.repaint();
        /*agrega el nodo al grafo*/
        Nodo nuevoNodo = new Nodo();
        nuevoNodo.setNombre(ci.numeroNodo);
        grafo.agregarNodo(nuevoNodo);
    }

    /**
     * Metodo que elimina un nodo y todas las aritas asociadas a ese nodo recibe
     * como parametro el nmero de nodo a eliminar, ATENCION tuvimso que utilziar
     * codigo poco performante porque de la manera mas apta lanzaba excepcion de
     * current modification y no sabiamos como solucionar
     *
     * @param nodoAEliminar
     */
    public void eliminarNodo(int nodoAEliminar) {
        ArrayList<Linea> arcosABorras = new ArrayList<Linea>();
        for (Circulo nodo : listaNodos) {
            if (nodo.numeroNodo == nodoAEliminar) {
                listaNodos.remove(nodo);
                grafo.eliminarNodo(nodoAEliminar);
                break;
            }
        }

        for (Linea arco : listaArcos) {
            if (arco.ffinal.getNumeroNodo() == nodoAEliminar) {
                arcosABorras.add(arco);
            }
            if (arco.inicial.getNumeroNodo() == nodoAEliminar) {
                arcosABorras.add(arco);
            }

        }

        for (Linea arco : arcosABorras) {
            listaArcos.remove(arco);
        }

        p.repaint();
    }

    /**
     *
     * SECCION DE CODIGO DEDICADA A MANEJAR LOS EVENTOS DEL RATON DENTRO DEL
     * LIENZO
     */
   
   
    public void mouseClicked(MouseEvent e)   {
           if (listaNodos.size()>=25){
            JOptionPane.showMessageDialog(null, "Solo se permiten 25 nodos!","Operacion no soportada",JOptionPane.ERROR_MESSAGE);
        }
        else{
            try {
            if (e.getButton() == 1) {
                x = e.getX();
                y = e.getY();
                agregarNodo(x, y);

            } else {
                if (e.getButton() == 3) {
                    int ini = Integer.parseInt(JOptionPane.showInputDialog("Numero de circulo inicual"));
                    int fin = Integer.parseInt(JOptionPane.showInputDialog("Numero de circulo final"));
                    agregarArco(ini, fin);

                }
            }
        }
        catch (CantidadNodosSuperada can){
           
        }
        catch (Exception ex) {
        }
        
        }
         

       
    }

    public void mousePressed(MouseEvent e) {
       
    }

    public void mouseReleased(MouseEvent e) {
        
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
    }

    public void mouseDragged(MouseEvent e) {


        if (jalada == null) {
            for (Circulo f : listaNodos) {
                if (f.jaladopor(e.getPoint())) {
                    jalada = f;
                }
                x = e.getPoint().x;
                y = e.getPoint().y;
                repaint();
                p.repaint();
            }
        } else {
            jalada.transladar(e.getPoint().x - x, e.getPoint().y - y);
            x = e.getPoint().x;
            y = e.getPoint().y;
            repaint();
            p.repaint();
        }

    }

    public void mouseMoved(MouseEvent e) {
        jalada = null;
    }
//////////////////////////////////////////////////////////////////////////////////////////////
    //pruea iamgen funciona perfecto

    public void imagenes(String nombre) {
        BufferedImage bi = new BufferedImage(this.getSize().width, this.getSize().height, BufferedImage.TYPE_INT_ARGB);
        Graphics g = bi.createGraphics();
        this.paint(g);  //this == JComponent
        g.dispose();
        try {
            ImageIO.write(bi, "png", new File("./Saves/"+nombre+".png"));
        } catch (Exception e) {
        }
    }

    /**
     * SECTOR DE CODIGO QUE ..
     */
    /**
     * Metodo que determian si existe un nodo, tomando como parametro su nombre
     * por le modleo d eprogramacion, si existe en la UI existe en la estructura
     * de datos
     *
     * @param nodoAbuscar
     * @return
     */
    public boolean existeNodo(int nodoAbuscar) {

        for (Circulo nodo : listaNodos) {
            if (nodo.getNumeroNodo() == nodoAbuscar) {
                return true;
            }
        }
        return false;
    }

    /**
     * Metodo que determian si un arco existe, recibe como parametro los dos
     * nodos que lo contienen
     *
     * @param nodoInicial
     * @param nodoFinal
     * @return
     */
    public boolean existeArco(int nodoInicial, int nodoFinal) {

        for (Linea arco : listaArcos) {
            if (arco.getInicial().getNumeroNodo() == nodoInicial && arco.getFfinal().getNumeroNodo() == nodoFinal) {
                return true;
            }
        }
        return false;
    }

    /*
     * SEGMENTO DE CODIGO DEDICADO AL TRABAJO CON XML
     */
    public boolean crearXML(String ruta) {
        if (!ruta.endsWith(".xml")) {
            ruta += ".xml";
        }
        CreaXMl creador = new CreaXMl(this.listaNodos, this.listaArcos);
        creador.crearXml(ruta);
        return true;
    }

    /**
     * Clase interna que lee el XML, la hice interna para que pueda acceder a
     * los metodos d elienzo y ais crear los nodos a medida que los lee
     */
    class LectorXML extends DefaultHandler {

        private final XMLReader xr;

        public LectorXML() throws SAXException {
            xr = XMLReaderFactory.createXMLReader();
            xr.setContentHandler(this);
            xr.setErrorHandler(this);
        }

        public void parsear(final String archivoXML)
                throws FileNotFoundException, IOException,
                SAXException {
            FileReader fr = new FileReader(archivoXML);
            xr.parse(new InputSource(fr));
        }

        public void startElement(String uri, String name,
                String qName, Attributes atts) {
            if (qName.equals("Nodo")) {//creo los nodos
                int numero = Integer.parseInt(atts.getValue("numero"));
                int posicionx = Integer.parseInt(atts.getValue("posicionx"));
                int posiciony = Integer.parseInt(atts.getValue("posiciony"));
                int peso = Integer.parseInt(atts.getValue("peso"));
                Circulo nodo = new Circulo(posicionx, posiciony);
                nodo.setNumeroNodo(numero);
                nodo.setPeso(peso);
                Lienzo.this.agregarNodo(nodo);

            }
            if (qName.equals("Arco")) {
                int numeroNodoInicial = Integer.parseInt(atts.getValue("arcoInicial"));
                int numeroNodoFinal = Integer.parseInt(atts.getValue("arcoFinal"));
                int peso = Integer.parseInt(atts.getValue("peso"));
                Lienzo.this.agregarArco(numeroNodoInicial, numeroNodoFinal, peso);

            }


        }
    }

    /**
     * Este metodo crea un objeto capaz de leer el xml y lo procesa
     */
    public void parsear(String ruta) {
        try {
            LectorXML lector = new LectorXML();
            lector.parsear(ruta);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     *
     * SIN NOMBRE TODAVIA
     */
    /*
     * Deja el lienzo como neuvo, borrando cualqueir componente pintado
     * o logica ne la estructura de dato grafo
     */
    public void nuevoLienzo() {
        this.listaArcos.clear();
        this.listaNodos.clear();
        Circulo.resetearAutoincremento();
        this.grafo = new GrafoNoDirigido();
        p.repaint();
    }
}
